/*
 * Copyright 2011 OpenWAF.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
/*
 * Original source from Google Web Toolkit(GWT)
 * http://code.google.com/webtoolkit/
 *
 * Modified to adapt OpenWAF Framework
 */
/*
 * Copyright 2008 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.openwaf.client.xml.impl;

import com.openwaf.client.core.Client;
import com.openwaf.client.core.JavaScriptException;
import com.openwaf.client.xml.Document;

/**
 * Native implementation associated with
 * {@link com.google.gwt.xml.client.XMLParser}.
 */
public class XMLParserImpl {

    static final Object domParser = createDOMParser();

    static Object createDOMParser(){
        if(Client.IE){
            return selectDOMDocumentVersion();
        }
        return createDOMParserStd();
    }
    private static native Object createDOMParserStd() /*-{
        return new DOMParser();
    }-*/;
    
    
    static native Object appendChild(Object jsObject,
            Object newChildJs) /*-{
        return jsObject.appendChild(newChildJs);
    }-*/;

    static native void appendData(Object jsObject, String arg) /*-{
        jsObject.appendData(arg);
    }-*/;

    static native Object cloneNode(Object jsObject,
            boolean deep) /*-{
        return jsObject.cloneNode(deep);
    }-*/;

    static native Object createCDATASection(Object jsObject,
            String data) /*-{
        return jsObject.createCDATASection(data);
    }-*/;

    static native Object createComment(Object jsObject,
            String data) /*-{
        return jsObject.createComment(data);
    }-*/;

    static native Object createDocumentFragment(
            Object jsObject) /*-{
        return jsObject.createDocumentFragment();
    }-*/;

    static native Object createElement(Object jsObject,
            String tagName) /*-{
        return jsObject.createElement(tagName);
    }-*/;

    static native Object createProcessingInstruction(
            Object jsObject, String target, String data) /*-{
        return jsObject.createProcessingInstruction(target, data);
    }-*/;

    static native Object createTextNode(Object jsObject,String data) /*-{
        return jsObject.createTextNode(data);
    }-*/;

    static native void deleteData(Object jsObject, int offset, int count) /*-{
        jsObject.deleteData(offset, count);
    }-*/;

    static native String getAttribute(Object o, String name) /*-{
        return o.getAttribute(name);
    }-*/;

    static native Object getAttributeNode(Object o,String name) /*-{
        return o.getAttributeNode(name);
    }-*/;

    static native Object getAttributes(Object t) /*-{
        return t.attributes;
    }-*/;

    static native Object getChildNodes(Object t) /*-{
        return t.childNodes;
    }-*/;

    static native String getData(Object o) /*-{
        return o.data;
    }-*/;

    static native Object getDocumentElement(Object o) /*-{
        return o.documentElement;
    }-*/;

    static Object getElementById(Object document, String id) {
        return getElementByIdImpl(document, id);
    }

    static Object getElementsByTagName(Object o,String tagName) {
        return getElementsByTagNameImpl(o, tagName);
    }

    static native int getLength(Object o) /*-{
        return o.length;
    }-*/;

    static native String getName(Object o) /*-{
        return o.name;
    }-*/;

    static native Object getNamedItem(Object t, String name) /*-{
        return t.getNamedItem(name);
    }-*/;

    static native String getNamespaceURI(Object jsObject) /*-{
        return jsObject.namespaceURI;
    }-*/;

    static native Object getNextSibling(Object o) /*-{
        return o.nextSibling;
    }-*/;

    static native String getNodeName(Object o) /*-{
        return o.nodeName;
    }-*/;

    static native short getNodeType(Object jsObject) /*-{
        var out = jsObject.nodeType;
        return (out == null) ? -1 : out;
    }-*/;

    static native String getNodeValue(Object o) /*-{
        return o.nodeValue;
    }-*/;

    static native Object getOwnerDocument(Object o) /*-{
        return o.ownerDocument;
    }-*/;

    static native Object getParentNode(Object o) /*-{
        return o.parentNode;
    }-*/;

    static String getPrefix(Object jsObject) {
        return getPrefixImpl(jsObject);
    }

    static native Object getPreviousSibling(Object o) /*-{
        return o.previousSibling;
    }-*/;

    static native boolean getSpecified(Object o) /*-{
        return o.specified;
    }-*/;

    static native String getTagName(Object o) /*-{
        return o.tagName;
    }-*/;

    static native String getTarget(Object o) /*-{
        return o.target;
    }-*/;

    static native String getValue(Object o) /*-{
        return o.value;
    }-*/;

    static native boolean hasAttributes(Object jsObject) /*-{
        return jsObject.attributes.length != 0;
    }-*/;

    static native boolean hasChildNodes(Object jsObject) /*-{
        return jsObject.hasChildNodes();
    }-*/;

    static Object importNode(Object jsObject,
            Object importedNode, boolean deep) {
        return importNodeImpl(jsObject, importedNode, deep);
    }

    static native Object insertBefore(Object jsObject,
            Object newChildJs, Object refChildJs) /*-{
        return jsObject.insertBefore(newChildJs, refChildJs);
    }-*/;

    static native void insertData(Object jsObject, int offset,String arg) /*-{
        jsObject.insertData(offset, arg);
    }-*/;

    static native Object item(Object t, int index) /*-{
        if (index >= t.length) {
            return null;
        }
        return t.item(index);
    }-*/;

    static native void normalize(Object jsObject) /*-{
        jsObject.normalize();
    }-*/;

    static native void removeAttribute(Object jsObject, String name) /*-{
        jsObject.removeAttribute(name);
    }-*/;

    static native Object removeChild(Object jsObject,Object oldChildJs) /*-{
        return jsObject.removeChild(oldChildJs);
    }-*/;

    static native Object removeNamedItem(Object jsObject,String name) /*-{
        return jsObject.removeNamedItem(name);
    }-*/;

    static native Object replaceChild(Object jsObject,Object newChildJs, Object oldChildJs) /*-{
        return jsObject.replaceChild(newChildJs, oldChildJs);
    }-*/;

    static native void replaceData(Object jsObject, int offset,int count, String arg) /*-{
        jsObject.replaceData(offset, count, arg);
    }-*/;

    static native void setAttribute(Object jsObject, String name,String value) /*-{
        jsObject.setAttribute(name, value);
    }-*/;

    static native void setData(Object jsObject, String data) /*-{
        jsObject.data = data;
    }-*/;

    static native Object setNamedItem(Object jsObject,Object arg) /*-{
        return jsObject.setNamedItem(arg);
    }-*/;

    static native void setNodeValue(Object jsObject, String nodeValue) /*-{
        jsObject.nodeValue = nodeValue;
    }-*/;

    static native Object splitText(Object jsObject, int offset) /*-{
        return jsObject.splitText(offset);
    }-*/;

    static native String substringData(Object o, int offset, int count) /*-{
    return o.substringData(offset, count);
    }-*/;

    /**
     * Not globally instantable.
     */
    XMLParserImpl() {
    }

    public static final Document createDocument() {
        return (Document) NodeImpl.build(createDocumentImpl());
    }

    public static final Document parse(String contents) {
        try {
            return (Document) NodeImpl.build(parseImpl(contents));
        } catch (JavaScriptException e) {
            throw new DOMParseException(contents, e);
        }
    }

    public boolean supportsCDATASection() {
        return true;
    }

    private static native Object selectDOMDocumentVersion() /*-{
        try { return new ActiveXObject("Msxml2.DOMDocument"); } catch (e) { }
        try { return new ActiveXObject("MSXML.DOMDocument"); } catch (e) { }
        try { return new ActiveXObject("MSXML3.DOMDocument"); } catch (e) { }
        try { return new ActiveXObject("Microsoft.XmlDom"); } catch (e) { }
        try { return new ActiveXObject("Microsoft.DOMDocument"); } catch (e) { }
        throw new Error("XMLParserImpl.createDocumentImpl: Could not find appropriate version of DOMDocument.");
    }-*/;

    private static Object createDocumentImpl(){
        if(Client.IE){
            return createDocumentImpl_IE();
        }
        return createDocumentImpl_Std();
    }

    private static native Object createDocumentImpl_Std() /*-{
        return document.implementation.createDocument("", "", null);
    }-*/;

    private static native Object createDocumentImpl_IE() /*-{
        var doc = @com.openwaf.client.xml.impl.XMLParserImpl::selectDOMDocumentVersion()();
        doc.preserveWhiteSpace = true;
        doc.setProperty("SelectionNamespaces", "xmlns:xsl='http://www.w3.org/1999/XSL/Transform'");
        doc.setProperty("SelectionLanguage", "XPath");
        return doc;
    }-*/;

    private  static Object getElementByIdImpl(Object document, String id){
        if(Client.IE){
            return getElementByIdImpl_IE(document, id);
        }
        return getElementByIdImpl_Std(document, id);
    }

    private static native Object getElementByIdImpl_Std(Object document, String id) /*-{
        return document.getElementById(id);
    }-*/;

    private static native Object getElementByIdImpl_IE(Object o,String elementId) /*-{
        return o.nodeFromID(elementId);
    }-*/;

    private static Object getElementsByTagNameImpl(Object o, String tagName){
        if(Client.IE){
            return getElementsByTagNameImpl_IE(o, tagName);
        }
        if(Client.SAFARI){
            return getElementsByTagNameImpl_Safari(o, tagName);
        }
        return getElementsByTagNameImpl_Std(o, tagName);
    }

    private static native Object getElementsByTagNameImpl_Std(Object o, String tagName) /*-{
        return o.getElementsByTagNameNS("*",tagName);
    }-*/;

    private static native Object getElementsByTagNameImpl_IE(Object o,String tagName) /*-{
        return o.selectNodes(".//*[local-name()='" + tagName + "']");
    }-*/;

    private static native Object getElementsByTagNameImpl_Safari(Object o,String tagName) /*-{
        return o.getElementsByTagName(tagName);
    }-*/;

    private static String getPrefixImpl(Object jsObject){
        if(Client.IE){
            return getPrefixImpl_IE(jsObject);
        }
        return getPrefixImpl_Std(jsObject);
    }

    private static String getPrefixImpl_Std(Object jsObject) {
        String fullName = XMLParserImpl.getNodeName(jsObject);
        if (fullName != null && fullName.indexOf(":") != -1) {
            return fullName.split(":", 2)[0];
        }
        return null;
    }

    private static native String getPrefixImpl_IE(Object jsObject) /*-{
        return jsObject.prefix;
    }-*/;

    private static Object importNodeImpl(Object jsObject,Object importedNode, boolean deep){
        if(Client.IE){
            return importNodeImpl_IE(jsObject, importedNode, deep);
        }
        if(Client.SAFARI){
            return importNodeImpl_Safari(jsObject, importedNode, deep);
        }
        return importNodeImpl_Std(jsObject, importedNode, deep);
    }

    private static native Object importNodeImpl_Std(Object jsObject,Object importedNode, boolean deep) /*-{
        return jsObject.importNode(importedNode, deep);
    }-*/;

    private static native Object importNodeImpl_IE(Object o,Object importedNode, boolean deep) /*-{
        return importedNode;
    }-*/;

    private static native Object importNodeImpl_Safari(Object jsObject,Object importedNode, boolean deep) /*-{
        if (@com.openwaf.client.xml.impl.XMLParserImpl::isSafari2LevelWebKit()()) {
            importedNode = importedNode.cloneNode(deep);
        }
        return jsObject.importNode(importedNode, deep);
    }-*/;

    public static  Object parseImpl(String contents){
        if(Client.IE){
            return parseImpl_IE(contents);
        }
        if(Client.SAFARI){
            return parseImpl_Safari(contents);
        }
        return parseImpl_Std(contents);
    }

    protected static native Object parseImpl_Std(String contents) /*-{
        var domParser = @com.openwaf.client.xml.impl.XMLParserImpl::domParser;
        var result = domParser.parseFromString(contents,"text/xml");
        var roottag = result.documentElement;
        if ((roottag.tagName == "parsererror") && (roottag.namespaceURI == "http://www.mozilla.org/newlayout/xml/parsererror.xml")) {
            throw new Error(roottag.firstChild.data);
        }
        return result;
    }-*/;

    protected static native Object parseImpl_IE(String contents) /*-{
    var doc = this.@com.openwaf.client.xml.impl.XMLParserImpl::createDocumentImpl_IE()();
    if(!doc.loadXML(contents)) {
        var err = doc.parseError;
        throw new Error("line " + err.line + ", char " + err.linepos + ":" + err.reason);
    } else {
        return doc;
    }
    }-*/;

    protected static native Object parseImpl_Safari(String contents) /*-{
    var domParser = @com.openwaf.client.xml.impl.XMLParserImpl::domParser;
    var result = domParser.parseFromString(contents,"text/xml");
    var parseerrors = result.getElementsByTagName("parsererror");
    if (parseerrors.length > 0) {
        var err = parseerrors.item(0);
        if (err.parentNode.tagName == 'body') {
            @com.openwaf.client.xml.impl.XMLParserImpl::throwDOMParseException(Ljava/lang/String;)(err.childNodes[1].innerHTML);
        }
    }
    return result;
    }-*/;

    static String toStringImpl(ProcessingInstructionImpl node){
        if(Client.IE){
            return toStringImpl_IE(node);
        }
        return toStringImpl_Std(node);

    }

    static String toStringImpl(NodeImpl node){
        if(Client.IE){
            return toStringImpl_IE(node);
        }
        return toStringImpl_Std(node);
    }

    private static String toStringImpl_IE(ProcessingInstructionImpl node) {
        return toStringImpl_IE((NodeImpl) node);
    }

    private static native String toStringImpl_IE(NodeImpl node) /*-{
        var jsNode = node.@com.openwaf.client.xml.impl.DOMItem::getJsObject()();
        return jsNode.xml;
    }-*/;

    private static String toStringImpl_Std(ProcessingInstructionImpl node) {
        return toStringImpl_Std((NodeImpl) node);
    }

    private static native String toStringImpl_Std(NodeImpl node) /*-{
    var jsNode = node.@com.openwaf.client.xml.impl.DOMItem::getJsObject()();
    return new XMLSerializer().serializeToString(jsNode);
    }-*/;
    private static boolean safari2LevelWebKit = (getWebKitVersion() <= 420);

    private static boolean isSafari2LevelWebKit() {
        return safari2LevelWebKit;
    }

    private static native int getWebKitVersion() /*-{
    var result = / AppleWebKit\/([\d]+)/.exec(navigator.userAgent);
    return ((result) ? parseInt(result[1]) : 0) || 0;
    }-*/;

    private static void throwDOMParseException(String message) {
        throw new DOMParseException(message);
    }
}
